#pragma once
#include "coro_concept/executor.hh"
#include <atomic>
#include <coroutine>

namespace wb
{
enum class resume_order_policy
{
    lifo,
    fifo,
};

class event
{
  public:
    struct awaiter
    {
        awaiter(const event& ev) noexcept : owner_event(ev)
        {
        }
        /**
         * @brief True if the event is set, else suspend this coroutine.
         */
        bool await_ready() const noexcept
        {
            return owner_event.is_set();
        }

        /**
         * @brief  Adding this coroutine to the list of awaiters. Return false
         * if the event has been set.
         * @param  h Coroutine handle
         */
        bool await_suspend(std::coroutine_handle<> h) noexcept;

        /**
         * @brief Nothing to do when resuming.
         */
        void await_resume() noexcept
        {
        }

        const event& owner_event;
        std::coroutine_handle<> awaiting_coroutine;
        awaiter* next{nullptr};
    };

    explicit event(bool is_set = false) noexcept;
    ~event() = default;

    event(const event&) = delete;
    event(event&&) = delete;
    event& operator=(const event&) = delete;
    event& operator=(event&&) = delete;

    awaiter operator co_await() const noexcept
    {
        return awaiter{*this};
    }

    bool is_set() const noexcept
    {
        return (event_state_.load(std::memory_order::acquire) == this);
    }

    /**
     * @brief Set the event and resume all awaiters. By default we use lifo
     * policy, since it's more efficient.
     */
    void set(resume_order_policy p = resume_order_policy::lifo) noexcept;

    /**
     * @brief Set the event and resume all awaiters on given executor. This will
     * dispatch the awaiters to the executor's thread.
     * @tparam T
     * @param  e XXX
     */
    template <concepts::executor T>
    void set(T& e, resume_order_policy p = resume_order_policy::lifo)
    {
        void* old_ptr = event_state_.exchange(this, std::memory_order::acq_rel);
        if (old_ptr == this)
        {
            // If resume policy is fifo, then we need to reverse the linked
            // list.
            if (p == resume_order_policy::fifo)
            {
                old_ptr = reverse(static_cast<awaiter*>(old_ptr));
            }

            auto* awaiters = static_cast<awaiter*>(old_ptr);
            while (awaiters != nullptr)
            {
                e.resume(awaiters->awaiting_coroutine);
                awaiters = awaiters->next;
            }
        }
    }

    /**
     * @brief Reset the event_state to that the event can be reused.
     */
    void reset() noexcept;

  private:
    awaiter* reverse(awaiter* head) noexcept;

  private:
    friend struct awaiter;
    /**
     * @brief When == nullptr, then it's not set; when == awaiter*, then it's
     * the linked list for events to trigger; when == this, then the event has
     * been triggered and all awaiters have resumed.
     */
    mutable std::atomic<void*> event_state_;
};
}  // namespace wb